package com.huilam.restactions.common.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.huilam.restactions.common.RESTAction;
import com.huilam.restactions.common.HttpReqClient;


public class ConfigUtil {
	
	public enum RequestMethod {
		  GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
	}
	
	private Properties propConfig = null;
	private String CONFIGPROP_FILENAME = "/restactions.properties";
	private static ConfigUtil instance = null;
	private boolean isReloadConfig = false;
	private List<String> listIgnoreUrlSuffix = null;
	
	private List<String> listRESTUrl = null;
	private Map<String, RESTAction> mapActionConfig = null ;
	private Map<Integer, Vector<String>> mapPaths = null;
	
	private ConfigUtil(String aPropFilePath)
	{
		CONFIGPROP_FILENAME = aPropFilePath;
		mapActionConfig = new HashMap<String, RESTAction>();
		listIgnoreUrlSuffix = new ArrayList<String>();
		listRESTUrl = new ArrayList<String>();
		reloadConfig();
	}
	
	private ConfigUtil()
	{
		reloadConfig();
	}
	
	public void reloadConfig()
	{
		if(propConfig==null)
			propConfig = new Properties();
		
		try{

			InputStream in  = null;
			try{
				in = ConfigUtil.class.getResourceAsStream(CONFIGPROP_FILENAME);
				if(in!=null)
				{
					propConfig.load(in);
					loadConfigs();
				}
			}finally
			{
				if(in!=null)
					in.close();
			}
			
		}catch(IOException ioEx)
		{
			ioEx.printStackTrace();
			throw new RuntimeException("fail to load "+CONFIGPROP_FILENAME);
		}
		
	}
	
	public static ConfigUtil getInstance()
	{
		if(instance==null)
			instance = new ConfigUtil();
		
		return instance;	
	}

	public RESTAction getRESTAction(HttpReqClient reqClient)
	{
		if(isReloadConfig)
			reloadConfig();
		
		String sReqUri 			= reqClient.getRequestUri();
		String sMethodReqUrl 	= reqClient.getRequestMethod();
		
		RESTAction action = mapActionConfig.get(sMethodReqUrl+"."+sReqUri);
		if(action==null)
		{
			String[] sReqPaths = reqClient.getRequestPath();
			
			Vector<String> vMatchedPaths = mapPaths.get(new Integer(sReqPaths.length));
			
			if(vMatchedPaths!=null)
			{
				for(int i=0; i<vMatchedPaths.size() && action==null; i++)
				{
					String sPath = ((String)vMatchedPaths.get(i));
					String sRegExpPath = sPath.replaceAll("\\{(.*?)\\}", ".+");

					Matcher m = Pattern.compile(sRegExpPath).matcher(sReqUri);
					if(m.matches())
					{
						action = mapActionConfig.get(sMethodReqUrl+"."+sPath);
					}
				}
			}
		}
		return action;
	}
	
	private void loadConfigs()
	{
		if(mapActionConfig==null)
			mapActionConfig = new HashMap<String, RESTAction>();
		mapActionConfig.clear();

		if(mapPaths==null)
			mapPaths = new HashMap<Integer, Vector<String>>();
		mapPaths.clear();
		
		if(listIgnoreUrlSuffix==null)
			listIgnoreUrlSuffix = new ArrayList<String>();
		listIgnoreUrlSuffix.clear();
		
		if(listRESTUrl==null)
			listRESTUrl = new ArrayList<String>();
		listRESTUrl.clear();
		
		
		isReloadConfig 	= "true".equals(propConfig.getProperty("system.properties.reload","false"));
		
		String sIgnoreSuffix = propConfig.getProperty("system.url.ignore.suffix","");
		if(!"".equals(sIgnoreSuffix))
		{
			StringTokenizer tk = new StringTokenizer(sIgnoreSuffix,",");
			while(tk.hasMoreTokens())
			{
				String sUrl = tk.nextToken();
				if(sUrl!=null)
				{
					listIgnoreUrlSuffix.add(sUrl.toLowerCase());
				}
			}
		}

		
		
		Enumeration e = propConfig.propertyNames();
		while(e.hasMoreElements())
		{
			String sPropKey = (String) e.nextElement();
			if(sPropKey.endsWith(".path"))
			{
				int iPos = sPropKey.length()-(".path".length());
				String sModuleId = sPropKey.substring(0,iPos);
				String sModulePath = propConfig.getProperty(sModuleId+".path");
						
				RESTAction restaction = getRESTAction(sModuleId);
				
				if(restaction!=null)
				{
					//validation
					if(mapActionConfig.get(sModulePath)!=null)
					{
						//duplicate module path entry
						throw new RuntimeException("[DuplicateError] "+sModulePath+" - "+mapActionConfig.get(sModulePath)+" "+restaction);
					}
					for(String sActionReqMethod : restaction.getRequestMethods())
					{
						mapActionConfig.put(sActionReqMethod+"."+sModulePath, restaction);
					}
					
					Integer iPathSeg = HttpUtils.parseUri(sModulePath).length;
					Vector v = mapPaths.get(iPathSeg);
					if(v==null)
						v = new Vector();
					v.add(sModulePath);
					
					mapPaths.put(iPathSeg, v);
				}
			}
		}
	}
	
	public RESTAction getRESTAction(String aModuleID)
	{
		RESTAction ra = null;
		String sModulePath 	= propConfig.getProperty(aModuleID+".path");
		if(sModulePath==null)
			return null;
		
		String sDefCfgPrefix 	= "default.config.";
		String defaultDebug 	= propConfig.getProperty(sDefCfgPrefix+"debug","false");
		String defaultEnabled 	= propConfig.getProperty(sDefCfgPrefix+"enabled","true");	
		String defaultAppRes 	= propConfig.getProperty(sDefCfgPrefix+"app.resources","");
		String defaultReqMethods 	= propConfig.getProperty(sDefCfgPrefix+"req.methods","GET");		
		//
		String sModulePrefix = aModuleID+".";
		String sClassName 	= propConfig.getProperty(sModulePrefix+"classname");
		boolean isDebugMode = "true".equalsIgnoreCase(propConfig.getProperty(sModulePrefix+"debug",defaultDebug));
		boolean isEnable 	= "true".equalsIgnoreCase(propConfig.getProperty(sModulePrefix+"enabled",defaultEnabled));

		String sAppResources = propConfig.getProperty(sModulePrefix+"app.resources", defaultAppRes);
		String sActionReqMethods = propConfig.getProperty(sModulePrefix+"req.methods", defaultReqMethods);		
		listRESTUrl.add(sModulePath);
		
		if(isEnable)
		{
			ra = new RESTAction(
					aModuleID, sModulePath, 
					getRequestMethods(sActionReqMethods));
			ra.setClassName(sClassName);
			ra.setDebugMode(isDebugMode);
			ra.setAppResources(sAppResources);
			
			Enumeration e2 = propConfig.propertyNames();
			while(e2.hasMoreElements())
			{
				String sCfgKey = (String) e2.nextElement();
				if(sCfgKey.startsWith(aModuleID))
				{
					String sCfgValue = propConfig.getProperty(sCfgKey);
					ra.addConfigProp(sCfgKey.substring(aModuleID.length()+1), sCfgValue);
				}
			}			
			//
			try{
				ra.getRESTActionClass();
			}catch(ClassNotFoundException ex)
			{
				throw new RuntimeException("[ERROR] Invalid classname:"+ra.getClassName()+ " - "+ra);
			}					
			//			
		}

		return ra;
	}

	public void printProps()
	{
		Enumeration e = propConfig.propertyNames();
		while(e.hasMoreElements())
		{
			String sPropKey = (String) e.nextElement();
			System.out.println(sPropKey+"="+propConfig.getProperty(sPropKey));
		}
	}
	
	public boolean genTemplate()
	{
		boolean isGenerated = true;
		
		List<String> listProp = new LinkedList<String>();
		
		listProp.add("# Global RESTAction configuration");
		listProp.add("system.config.reload-on-changed=true");
		listProp.add(" ");
		listProp.add("default.config.enabled=true");
		listProp.add("default.config.debug=false");
		listProp.add("default.config.auth.required=false");
		listProp.add("default.config.auth.classname=com.huilam.restactions.services.auths.DefaultSimpleAuth");
		//
		listProp.add(" ");
		listProp.add("# Sample - echo");
		listProp.add("echo.path=/echo/{message}");
		listProp.add("echo.classname=com.huilam.restactions.services.Echo");
		//
		listProp.add(" ");
		listProp.add("# Sample - time");
		listProp.add("time.path=/time/{format}");
		listProp.add("time.classname=com.huilam.restactions.services.actions.ServerTime");		
		
				
		
		return isGenerated;
	}
	
	public Vector<String> getRequestMethods(String aReqMethods)
	{
		Vector<String> v = new Vector<String>();
		if(aReqMethods!=null && aReqMethods.length()>0)
		{
			StringTokenizer tk = new StringTokenizer(aReqMethods,",");
			while(tk.hasMoreTokens())
			{
				String sMethod = tk.nextToken().trim().toUpperCase();
				v.add(sMethod);
			}
		}
		return v;
	}
	
	public void printConfig()
	{
		Iterator iter = mapActionConfig.values().iterator();
		while(iter.hasNext())
		{
			RESTAction action = (RESTAction)iter.next();
			System.out.println(action);
		}
	}
	
	public static void main(String args[])
	{
		ConfigUtil.getInstance().printConfig();
	}
}
